package whendone

/**
  Functions that manipulate projects.
  @author Henrik Huttunen
*/
object Project{
  import sbinary._
  import sbinary.Operations._
  import ProjectBinary._
  /** Moves project from its parent to newParent.
      project.parent is set to newParent; project is removed from its original parent
      project is added to newparent's projects. 
  */
  def moveProject(project: Project, newParent: Project) = {
    val Some(parent) = project.parent
    parent.removeProject(project)
    newParent.addProject(project)
    project.setParent(Some(newParent))
  }
  /** Deletes the links between project and its parent. 
      @returns None if no parent, else Some(parent) 
  */
  def delete(project: Project) = project.parent match {
    case None => None
    case Some(parent) =>
      project.setParent(None)
      parent.removeProject(project)
      Some(parent)
  }
  /** Saves project to file using SBinary. */
  def saveToFile(project: Project)(implicit file: java.io.File) = {
    Operations.toFile(project)(file)
  }
  /** Loads project from file using SBinary. If file doesn't exist, throws RuntimeException */
  def loadFromFile(implicit file: java.io.File) = {
    if(!file.exists) throw new RuntimeException("File doesn't exists: " + file) 
    val project = Operations.fromFile[Project](file)
    project.setParent(None) // main project has no parents
    setSubProjectsParent(project)
    project
  }
  /** Sets parent for all projects recursively. 
      Is needed because of SBinary's incapability of handling bidirectional references. */
  def setSubProjectsParent(parent: Project): Unit = {
    for(project <- parent.projects){
      project.setParent(Some(parent))
      setSubProjectsParent(project)
    }
  }
}

/**
  Log entry that has description and time of creation.
  @author Henrik Huttunen
*/
class Entry(val description: String, val date: java.util.Date){
  def this(description: String) = this(description, new java.util.Date)
}

/**
  Project that consists of subprojects, entries of logging, and tag attributes for searching. It has name, and optional parent.
  @author Henrik Huttunen
*/
class Project(private var parent_ : Option[Project], var name: String){

  private var subProjects_ : List[Project] = Nil
  private var entries_ : List[Entry] = Nil
  private var attributes_ : List[String] = Nil

  /** Subprojects */
  def projects: List[Project] = subProjects_
  /** Log entries */
  def entries: List[Entry] = entries_
  /** Possible parent project */
  def parent: Option[Project] = parent_
  /** Attributes */
  def attributes = attributes_

  /** Adds the given attribute.
      precondition: !attributes.contains(attribute) 
  */
  def addAttribute(attribute: String) = {
    attributes_ = attributes_ ::: List(attribute)
  }
  /** Removes the given attribute */
  def removeAttribute(attribute: String) = {
    attributes_ = attributes_ remove(_ == attribute) 
  }
  /** Adds log entry with the given description. */
  def addEntry(description: String): Unit = addEntry(new Entry(description))
  /** Adds given log entry. */
  def addEntry(entry: Entry) = entries_ :::= List(entry)
  /**  Adds new project if there's no project named 'name'.
       @returns project named 'name' 
  */
  def addSubProject(name: String) = {
    subProjects_.find{x => x.name == name} match {
      case None =>
        val subProject = new Project(Some(this), name)
        addProject(subProject)
        subProject
      case Some(project) => project
    }
  }
  /** Adds project as subproject. */
  def addProject(project: Project) = {
    subProjects_ :::= List(project)
    subProjects_ = subProjects_ sort((e1, e2) => (e1.name compareTo e2.name) < 0)  
  }
  /** Removes project. */
  private def removeProject(project: Project) = { subProjects_ = subProjects_ remove(_ == project)  }
  /** Sets parent. */
  private def setParent(parent: Option[Project]) = parent_ = parent
}
